﻿using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;

namespace SenGe.Application.Services
{
    /// <summary>
    /// .net core缓存服务
    /// 本缓存服务存在1-10分钟的缓存偏移量，所以对数据精确控制的，需要及时清理指定缓存
    /// </summary>
    public class MemoryCacheService : ICacheService
    {
        /// <summary>
        /// 本地内存缓存接口
        /// </summary>
        private readonly IMemoryCache _cache;
        /// <summary>
        /// 日志对象
        /// </summary>
        private readonly ILogger<MemoryCacheService> _logger;
        /// <summary>
        /// 缓存键锁字典
        /// </summary>
        private readonly ConcurrentDictionary<string, SemaphoreSlim> _keyLocks = new();
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cache"></param>
        /// <param name="logger"></param>
        public MemoryCacheService(IMemoryCache cache, ILogger<MemoryCacheService> logger)
        {
            _cache = cache;
            _logger = logger;
        }

        /// <summary>
        /// 在缓存中查找缓存对象
        /// </summary>
        /// <typeparam name="T">数据实体对象</typeparam>
        /// <param name="key">缓存键</param>
        /// <returns></returns>
        public Task<T?> GetAsync<T>(string key)
        {
            try
            {
                if (_cache.TryGetValue(key, out T? cachedValue))
                {
                    return Task.FromResult(cachedValue);
                }
                return Task.FromResult(default(T));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error getting cache for key: {Key}", key);
                return Task.FromResult(default(T));
            }
        }

        /// <summary>
        /// 设置数据缓存
        /// </summary>
        /// <typeparam name="T">数据实体对象</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="value">数据对象</param>
        /// <param name="expiration">过期时间</param>
        /// <returns></returns>
        public Task SetAsync<T>(string key, T value, TimeSpan? expiration = null)
        {
            try
            {
                var options = CreateCacheEntryOptions(expiration);
                _cache.Set(key, value, options);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error setting cache for key: {Key}", key);
            }
            return Task.CompletedTask;
        }

        /// <summary>
        /// 判断指定缓存键是否已存在
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns></returns>
        public Task<bool> ExistsAsync(string key)
        {
            return Task.FromResult(_cache.TryGetValue(key, out _));
        }

        /// <summary>
        /// 通过缓存键移除缓存对象
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns></returns>
        public Task RemoveAsync(string key)
        {
            try
            {
                _cache.Remove(key);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error removing cache for key: {Key}", key);
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// 获取或新建缓存
        /// </summary>
        /// <typeparam name="T">数据实体对象</typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="factory">数据工厂</param>
        /// <param name="expiration">过期时间</param>
        /// <returns></returns>
        public async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
        {
            // 1. 先尝试从缓存获取
            var cachedValue = await GetAsync<T>(key);
            if (!IsDefaultValue(cachedValue))
            {
                return cachedValue;
            }

            // 2. 防止缓存穿透：检查空值标记
            if (await IsNullKey(key))
            {
                return default;
            }

            // 3. 获取键级别的锁对象（防止缓存击穿）
            var keyLock = _keyLocks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));

            await keyLock.WaitAsync();
            try
            {
                // 4. 双重检查
                cachedValue = await GetAsync<T>(key);
                if (!IsDefaultValue(cachedValue))
                {
                    return cachedValue;
                }

                // 5. 调用工厂方法获取数据
                var value = await factory();

                if (IsDefaultValue(value))
                {
                    // 缓存空值，防止缓存穿透
                    await SetNullKey(key, expiration ?? TimeSpan.FromMinutes(5));
                    return default;
                }

                // 6. 设置缓存（使用随机过期时间防止雪崩）
                await SetAsync(key, value, expiration);
                return value;
            }
            finally
            {
                keyLock.Release();
                CleanupKeyLock(key, keyLock);
            }
        }

        /// <summary>
        /// 生成缓存配置项，自动添加1-10分钟的随机偏移量
        /// 默认缓存10分钟
        /// </summary>
        /// <param name="expiration"></param>
        /// <returns></returns>
        private MemoryCacheEntryOptions CreateCacheEntryOptions(TimeSpan? expiration)
        {
            var options = new MemoryCacheEntryOptions();

            if (expiration.HasValue)
            {
                // 随机过期时间，防止缓存雪崩
                var randomExpiration = GetRandomExpiration(expiration.Value);
                options.SetAbsoluteExpiration(randomExpiration);
            }
            else
            {
                options.SetSlidingExpiration(TimeSpan.FromMinutes(10));
            }

            // 注册缓存项移除回调
            options.RegisterPostEvictionCallback(OnCacheItemRemoved);

            return options;
        }
        /// <summary>
        /// 缓存过期回调函数
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="reason"></param>
        /// <param name="state"></param>
        private void OnCacheItemRemoved(object key, object? value, EvictionReason reason, object? state)
        {
            _logger.LogDebug("Cache item removed: {Key}, Reason: {Reason}", key, reason);

            // 清理对应的锁对象
            if (key is string keyString)
            {
                _keyLocks.TryRemove(keyString, out _);
            }
        }

        /// <summary>
        /// 在缓存过期时间上添加偏移量
        /// 目前采用在原缓存时间上新增1-10分钟的随机偏移量
        /// </summary>
        /// <param name="baseExpiration">缓存过期时间</param>
        /// <returns></returns>
        private TimeSpan GetRandomExpiration(TimeSpan baseExpiration)
        {
            // 在基础过期时间上增加随机偏移，防止大量缓存同时过期造成缓存雪崩
            var random = new Random();
            var offsetMinutes = random.Next(1, 10);
            return baseExpiration.Add(TimeSpan.FromMinutes(offsetMinutes));
        }

        /// <summary>
        /// 判断指定缓存键是否是空值键
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns></returns>
        private Task<bool> IsNullKey(string key)
        {
            var nullKey = $"null_{key}";
            return ExistsAsync(nullKey);
        }
        /// <summary>
        /// 将指定缓存键设置为空值键
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="expiration">过期时间</param>
        /// <returns></returns>
        private Task SetNullKey(string key, TimeSpan expiration)
        {
            var nullKey = $"null_{key}";
            return SetAsync(nullKey, true, expiration); //此处true可以替换为其他任意值对象
        }

        /// <summary>
        /// 判断数据对象是否是默认值
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="value">数据对象</param>
        /// <returns></returns>
        private bool IsDefaultValue<T>(T value)
        {
            return EqualityComparer<T>.Default.Equals(value, default);
        }

        /// <summary>
        /// 清除缓存键锁
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="keyLock">缓存键锁</param>
        private void CleanupKeyLock(string key, SemaphoreSlim keyLock)
        {
            // 如果没有等待的线程，清理锁对象
            if (keyLock.CurrentCount == 1)
            {
                _keyLocks.TryRemove(key, out _);
            }
        }

        /// <summary>
        /// 清除所有缓存
        /// </summary>
        public void Clear()
        {
            if (_cache is MemoryCache memoryCache)
            {
                memoryCache.Compact(1.0); // 清除所有缓存项
            }
        }

        /// <summary>
        /// 获取缓存项数
        /// </summary>
        /// <returns></returns>
        public long GetCount()
        {
            if (_cache is MemoryCache memoryCache)
            {
                // 注意：这需要反射获取内部计数，实际项目中可能需要其他方式
                return memoryCache.Count;
            }
            return 0;
        }
    }
}
